/*
 * $Id:
 *
 * Copyright (C) 2011 Refiner Inc.
 * All right reserved
 *
 *============================================================
 * +rVPN Stack : AppIF (Socket Base Interface)
 *============================================================
 * abstract: TCP/IPスタック側のソケットI/F処理
 * author  : MURATA
 * history :
 *
 */
#include <sys/select.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include "rvs_appif.h"
#include "rvs_socket.h"
#include "rsi_sockif.h"
#include "rsi_apisock.h"
#include "rsi_tcpipif.h"
#include <rts_socket.h>

#define	RSI_THR_TIMEOUT 	10    /* 受信スレッド起動タイムアウト(秒) */
#define RSI_THR_WAIT   		100   /* 受信スレッド起動待ちsleep時間(ミリ秒) */
#define	RSI_THR_WAITLOOP	(RSI_THR_TIMEOUT*1000/RSI_THR_WAIT)
#define RSI_SEL_TIMEOUT	100   /* select タイムアウト(ミリ秒) */

static pthread_t recv_tid=-1;
static int lissock = INVALID_SOCKET;

rvs_conf_t rsi_conf;

static int
thread_create (pthread_t *tid, void *(*func)(void *), void *arg, int size)
{
    pthread_attr_t  attr;

    if (size) {
        pthread_attr_init (&attr);
        pthread_attr_setstacksize (&attr, size);
    }
    return pthread_create (tid, size ? &attr : NULL, func, arg);
}

/*------------------------------------*
 * 受信スレッド
 *------------------------------------*/
static void* rsi_recv_thread(void *param)
{
    int nfds;
    fd_set savefds;
    int* run = (int*)param;
    int on = 1;

    /* listenソケットの生成 */
    if (rsi_conf.staddr.sa.sa_family == AF_INET) {
        lissock = socket (PF_INET, SOCK_STREAM, 0);
    } else {
        lissock = socket (PF_UNIX, SOCK_STREAM, 0);
        unlink (rsi_conf.staddr.saun.sun_path);
    }
    if (lissock < 0) {
        lissock = INVALID_SOCKET;
        pthread_exit(NULL);
    }

    setsockopt(lissock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));

    /* 非ブロッキングモードに設定 */
    if (fcntl (lissock, F_SETFL, O_NONBLOCK) < 0) {
        LOGE("%s: fcntl - %s\n", __FUNCTION__, strerror(errno));
        close(lissock);
        lissock = INVALID_SOCKET;
        pthread_exit(NULL);
    }

    /* bind */
    if (bind (lissock, &rsi_conf.staddr.sa, rsi_conf.addrlen) < 0) {
        LOGE("%s: bind - %s\n", __FUNCTION__, strerror(errno));
        close(lissock);
        lissock = INVALID_SOCKET;
        pthread_exit(NULL);
    }

    /* listen */
    if (listen (lissock, RVS_MAX_APPIF) < 0) {
        LOGE("%s: listen - %s\n", __FUNCTION__, strerror(errno));
        close(lissock);
        lissock = INVALID_SOCKET;
        pthread_exit(NULL);
    }

    FD_ZERO(&savefds);
    FD_SET(lissock, &savefds);
    nfds = lissock+1;

    *run=1;
    while (!rsi_conf.exit) {
        fd_set readfds = savefds;
        struct timeval timeout;

        timeout.tv_sec = RSI_SEL_TIMEOUT/1000;
        timeout.tv_usec = (RSI_SEL_TIMEOUT%1000)*1000;
        int rfds = select (nfds, &readfds, NULL, NULL, &timeout);

        if (rfds < 0) {
            if (errno == EINTR) {
                break;
            }
            LOGE("%s: select - %s\n", __FUNCTION__, strerror(errno));
            continue;
        }

        if (rfds == 0) {
            /* Timeout */
            continue;
        }

        /* 接続要求か？ */
        if (FD_ISSET(lissock, &readfds)) {
            struct sockaddr_storage sa;
            socklen_t salen = sizeof(struct sockaddr_storage);
            int nsock = accept (lissock, (struct sockaddr*)&sa, &salen);
            if (nsock >= 0) {
                LOGD("%s: accept - socket=%d\n", __FUNCTION__, nsock);

                /* NODELAY属性の設定 */
                if (rsi_conf.staddr.sa.sa_family == AF_INET) {
                    if (setsockopt(nsock, IPPROTO_TCP, TCP_NODELAY,
                                           (char *)&on, sizeof(on)) < 0) {
                         LOGE("%s: setsockopt(TCP_NODELAY): %s\n",
                                           __FUNCTION__, strerror(errno));
                    }
                }

                /* 新しいソケットの登録 */
                if (rsi_apisock_new(nsock, (struct sockaddr*)&sa, salen) < 0) {
                    close(nsock);
                } else {
                    /* selectするFDに追加する */
                    FD_SET(nsock, &savefds);
                    if (nsock >= nfds) {
                        nfds = nsock+1;
                    }
                }
            } else {
                LOGE("%s: accept - %s\n", __FUNCTION__, strerror(errno));
            }
            rfds--;
        } 

        /* 受信処理 */
        if (rfds > 0) {
            rsi_sock_t* pos = rsi_apisock_head();
            while (pos && rfds > 0) {
                rsi_sock_t* next = rsi_apisock_next(pos);
                int sock = rsi_apisock_get(pos);
                if (sock < 0) {
                    break;
                }
                if (FD_ISSET(sock, &readfds)) {
                    if (rsi_apisock_recv(pos) == RVSA_EOF) {
                        LOGD("%s: stop - socket=%d\n", __FUNCTION__, sock);
                        rsi_apisock_free(pos);
                        close(sock);
                        FD_CLR(sock, &savefds);

                        // 2011.09.15 koyama
                        // nfds の値を再設定
                        if ((sock+1) == nfds)
                        {
                            nfds--;
                            for ( ; nfds>0; nfds--)
                            {
                                if (FD_ISSET(nfds-1, &savefds))
                                {
                                    break;
                                }
                            }
                        }
                    }
                    rfds--;
                }
                pos = next;
            }
        }
    }

    *run=0;
    close(lissock);
    LOGD("rsi_recv_thread stop\n");

    return NULL;
}


/*------------------------------------*
 * 初期化／終了 関数定義                     *
 *------------------------------------*/
/* ソケットベースI/Fの初期化 */
int rsi_initialize(struct sockaddr* addr)
{
    int i;
    static int running=0;

    /* 初期化済みならいったん終了する */
    if (rsi_conf.init) {
        rsi_uninitialize();
    }

    /* 接続先の保存 */
    if (addr) {
        if (addr->sa_family == AF_UNIX) {
            rsi_conf.addrlen = sizeof(struct sockaddr_un);
            memcpy (&rsi_conf.staddr.saun, addr, rsi_conf.addrlen);
        } else if (addr->sa_family == AF_INET) {
            rsi_conf.addrlen = sizeof(struct sockaddr_in);
            memcpy (&rsi_conf.staddr.sain, addr, rsi_conf.addrlen);
        } else {
            rvs_set_errno(EINVAL);
            return -1;
        }
    } else {
#if RVS_AF_UNIX
        rsi_conf.addrlen = sizeof(struct sockaddr_un);
        memset (&rsi_conf.staddr.saun, 0, rsi_conf.addrlen);
        rsi_conf.staddr.saun.sun_family = AF_UNIX;
        strncpy(rsi_conf.staddr.saun.sun_path, RVS_API_SOCK_PATH,
                     sizeof(rsi_conf.staddr.saun.sun_path) - 1);
#else
        rsi_conf.addrlen = sizeof(struct sockaddr_in);
        memset(&rsi_conf.staddr, 0, rsi_conf.addrlen);
        rsi_conf.staddr.sain.sin_family = AF_INET;
        rsi_conf.staddr.sain.sin_addr.s_addr = inet_addr("127.0.0.1");
        rsi_conf.staddr.sain.sin_port = htons(RVS_API_SOCK_PORT);
#endif
    }

    /* TCP/IP初期化および応答関数の登録 */
    rsi_tif_init();

    /* ソケット管理部の初期化 */
    rsi_apisock_init();

    /* 受信スレッドの起動 */
    if (thread_create(&recv_tid, rsi_recv_thread, &running, 0) < 0) {
        LOGE("%s: thread_create - %s\n", __FUNCTION__, strerror(errno));
        recv_tid=-1;
        return -1;
    }

    /* 受信スレッドの起動を待つ */
    for (i=0; i<RSI_THR_WAITLOOP; i++) {
        if (running)  break;
        usleep(RSI_THR_WAIT*1000);
    }
    if (i==RSI_THR_WAITLOOP) {
        LOGE("%s: wait thread start - timeout\n", __FUNCTION__);
        return -1;
    }

    rsi_conf.init = 1;

    return 0;
}


/* ソケットベースI/Fの終了 */
void rsi_uninitialize(void)
{
    void* ret;

    if (!rsi_conf.init) {
        return;
    }

    rsi_conf.init = 0;
    rsi_conf.exit = 1;

    /* 受信スレッドが終了するまで待つ */
    pthread_join(recv_tid, &ret);

    recv_tid = -1;
    rsi_conf.exit=0;

    rsi_apisock_stop();
    rsi_tif_stop();
}

